package foundation.zk.crud.composer;

import static foundation.zk.crud.composer.AbstractCRUDEditComposer.CALLBACK_ARGUMENT;
import static foundation.zk.crud.composer.AbstractCRUDEditComposer.OBJECT_TO_EDIT_ARGUMENT;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.event.ForwardEvent;
import org.zkoss.zul.Grid;
import org.zkoss.zul.Label;
import org.zkoss.zul.ListModelList;
import org.zkoss.zul.Messagebox;
import org.zkoss.zul.Window;

import foundation.common.util.CommonWebUtil;
import foundation.web.flow.AbstractNavigationComposer;
import ar.com.sne.bean.BaseBO;
import coop.tecso.foundation.common.macros.FiltroBusquedaMacro;
import coop.tecso.foundation.common.macros.ValueImage;
/**
 * This class implements the abstract behaviour of CRUD manager composer
 * 
 * A CRUD manager is the user entry point to handle persistent objects.
 * 
 */
@SuppressWarnings("unchecked")
public abstract class AbstractCRUDManagerComposer extends
AbstractNavigationComposer {

	private static final long serialVersionUID = 8651429086862100425L;
	private final Log log = LogFactory.getLog(getClass());

	public Grid grdSearchResult;
	private List<Object> objectReturnList = new ArrayList<Object>();

	private EventListener callback;
	
	private Label labelNotResult;
	
	private Boolean showedLabelResult=true;

	@Override
	public void doAfterCompose(Component comp) throws Exception {
		
		super.doAfterCompose(comp);		
		Map<String, Object>args = Executions.getCurrent().getArg();
		EventListener callbackTemp = (EventListener) args.get(CALLBACK_ARGUMENT);
		if(callbackTemp!=null){
			callback=callbackTemp;
		}
		
		Grid grillaFiltros = (Grid)this.self.getFellowIfAny("grillafiltros");
		if(grillaFiltros!=null){
			loadListenerOnOkEvent(grillaFiltros, new EventListener() {				
				@Override
				public void onEvent(Event arg0) throws Exception {
						onClick$searchButton();			
					
				}
			});
		}
		
		Grid grillafiltrosAvanzados = (Grid)this.self.getFellowIfAny("grillafiltrosAvanzados");
		if(grillafiltrosAvanzados!=null){
			loadListenerOnOkEvent(grillafiltrosAvanzados, new EventListener() {				
				@Override
				public void onEvent(Event arg0) throws Exception {
						onClick$searchButton();			
					
				}
			});
		}
	}
	

	
	private void loadListenerOnOkEvent(Component component, EventListener eventListener){
		component.addEventListener(Events.ON_OK,eventListener);
		for(Object o : component.getChildren()){
			if(o instanceof Component && !(o instanceof FiltroBusquedaMacro)){
				Component subComponent = (Component)o;
				loadListenerOnOkEvent(subComponent,eventListener);
			}			
		}
	}	
	
	// //////////////////////////////////////
	// ///////// SEARCH /////////////////////
	// /////////////////////////////////////
	/**
	 * Search button click handler
	 */
	public void onClick$searchButton() {
		log.debug("search button clicked");
		resetGridModel(search());
		this.setLabelNotResult();
	}
	
	public void setLabelNotResult(){
		if(this.getShowedLabelResult()){
			ListModelList model = (ListModelList)grdSearchResult.getModel();		
			if(model!=null && model.size()==0){
				if(labelNotResult==null)
					labelNotResult=new Label();
				labelNotResult.setValue(CommonWebUtil.getProperty("AbstractCRUDManagerComposer.search.notResult"));
				if(grdSearchResult!=null)
					labelNotResult.setParent(this.grdSearchResult.getParent());			
			}
			else if(model!=null && model.size()>0){
				if(labelNotResult==null)
					labelNotResult=new Label();
				labelNotResult.setValue("");
				if(grdSearchResult!=null)
					labelNotResult.setParent(this.grdSearchResult.getParent());			
			}
			else if(objectReturnList!=null && objectReturnList.size()==0){
				if(labelNotResult==null)
					labelNotResult=new Label();
				labelNotResult.setValue(CommonWebUtil.getProperty("AbstractCRUDManagerComposer.search.notResult"));
				if(grdSearchResult!=null)
					labelNotResult.setParent(this.grdSearchResult.getParent());
			}
			else{
				this.clearLabelNotResult();
			}
		}

	}
	
	public void clearLabelNotResult(){
		if(labelNotResult!=null)
			labelNotResult.setValue("");
	}

	/**
	 * Perform the search using the filter criteria
	 * 
	 * @return the persistenObjects list whose apply the search criteria
	 */
	protected abstract List search();


	@Override
	public void callMethods() {
		onClick$searchButton();
	}
	// ////////////////////////////////////////////////////
	// ////////// CLEAR SEARCH CRITERIA ///////////////////
	// ////////////////////////////////////////////////////

	/**
	 * Clear Filters button click handler
	 */
	public void onClick$clearFiltersButton(Event event) {
		log.debug("clear filter button clicked");

		resetGridModel(null);
		clearLabelNotResult();
		// clear filter criteria fields
		clearFilterCriteriaFields();
	}

	/**
	 * Cleans the filter criteria input fields
	 */
	protected abstract void clearFilterCriteriaFields();

	// ////////////////////////////////////////////////////
	// //////////////// DELETE //////////////////////////////
	// ////////////////////////////////////////////////////
	/**
	 * Delete button click handler
	 */
	public void onClick$deleteButton(ForwardEvent event)
			throws InterruptedException {
		log.debug("delete button clicked");

		BaseBO objectToDelete = (BaseBO) getSelectedObject(event);
		confirmDelete(objectToDelete);
	}

	/**
	 * Ask the user for YES/NO confirmation before deleting the object. If the
	 * user answer YES the object is deleted calling abtract deleteObject()
	 * method
	 * 
	 * @param objectToDelete
	 *            the object to being deleted if the user answer YES
	 * @throws InterruptedException
	 *             if any error occurs
	 */
	private void confirmDelete(final BaseBO objectToDelete)
			throws InterruptedException {

		StringBuilder message = new StringBuilder();
		message
				.append(CommonWebUtil
						.getProperty("AbstractCRUDManagerComposer.confirm.delete.window.message"));
		message.append(" ");
		message.append(getObjectDescription(objectToDelete));

		Messagebox
				.show(
						message.toString(),
						CommonWebUtil
								.getProperty("AbstractCRUDManagerComposer.confirm.delete.window.title"),
						Messagebox.YES | Messagebox.NO, Messagebox.QUESTION,
						new EventListener() {
							public void onEvent(Event evt) {
								switch (((Integer) evt.getData()).intValue()) {
								case Messagebox.YES:
									deleteObject(objectToDelete);
									onClick$searchButton();
									break; // the Yes button is pressed
								}
							}

						});
	}

	/**
	 * Answer a description string for the given object. this description will
	 * be shown in delete confirmation window
	 * 
	 * @param objectToDelete
	 *            the object to generate the description
	 * @return
	 */
	protected abstract String getObjectDescription(
			BaseBO objectToDelete);

	/**
	 * Performs the delete action against the service layer
	 * 
	 * @param objectToDelete
	 *            object to be deleted
	 */
	protected abstract void deleteObject(BaseBO objectToDelete);

	
	// ////////////////////////////////////////////////
	// ////////////// Select Entity ///////////////////
	// // when admin page is used for advanced entity /
	// // search from other pages                     /
	// ////////////////////////////////////////////////
	
	
	public boolean isSelectingObject(){
		return callback != null;
	}
	
	public boolean isNotSelectingObject(){
        return callback == null;
}
	
	
	
	public void onSelectObjectButton(ForwardEvent forwardEvent)throws Exception{
		this.onClick$selectButton(forwardEvent);
	}
	
	public void onClick$selectButton(ForwardEvent event) throws Exception{
		log.debug("select button clicked");
		Executions.getCurrent().getDesktop().setAttribute("selectedObject", getSelectedObject(event));
		Window modalWindow = (Window) Executions.getCurrent().getDesktop().getAttribute(this.getClass().getSimpleName());
		if(modalWindow==null){
			self.detach();
		}
		if(modalWindow!=null){
			modalWindow.detach();
		}			
		Executions.getCurrent().getDesktop().removeAttribute(this.getClass().getSimpleName());
		callback.onEvent(event);				
	}
	
	public void onClick$btnVolver(Event event){
		this.self.detach();
	}
	
	// ////////////////////////////////////////////////
	// ////////////// EDIT / ADD //////////////////////
	// //add is implemented as editing a null object///
	// ////////////////////////////////////////////////
	/**
	 * Add button click handler
	 */
	public void onClick$addButton(Event event) {

		log.debug("add button clicked");
		openEditWindow(null);
	}

	/**
	 * Edit button click handler
	 */
	public void onClick$editButton(ForwardEvent event) {

		log.debug("edit button clicked");
		openEditWindow(getSelectedObject(event));
	}
	



	/**
	 * Opens an Edit window for the given parameter.
	 * 
	 * @param objectToedit
	 *            null or an object to be edited in the edit window. If
	 *            objectToedit is null the Edit window will add a new object
	 *            after a successful execution
	 */
	private void openEditWindow(Object objectToedit) {
		String url = CommonWebUtil.getProperty(getEditPageName());

		Map<String, Object> args = new HashMap<String, Object>();
		args.put(OBJECT_TO_EDIT_ARGUMENT, objectToedit);
		args.put(CALLBACK_ARGUMENT, new EventListener() {
			@Override
			public void onEvent(Event event) throws Exception {
				onClick$searchButton();
			}
		});

		Window winAdd = (Window) Executions.createComponents(url, this.self,
				args);

		try {
			winAdd.doHighlighted();
		} catch (Exception e) {
			log.error("Error opening modal EDIT window", e);
		    throw new RuntimeException(e);
		}
	}

	/**
	 * Answer the edit page name for the given CRUD manager
	 */
	protected abstract String getEditPageName();

	// ////////////////////////////////////////////////
	// //////////// SUPPORT METHODS //////////////////
	// //////////////////////////////////////////////

	/**
	 * Answer the object contained in an event triggered by a ValueImage
	 * 
	 * @return the ValueImage object value
	 */
	public Object getSelectedObject(ForwardEvent event) {

		return ((ValueImage) event.getOrigin().getTarget()).getValue();
	}

	/**
	 * Update the grid with the contents of elementsToShow if elementsToShow is
	 * null the grid will be empty
	 * 
	 * @param elementsToShow
	 *            null or the list to be shown in the grid
	 */
	private void resetGridModel(List elementsToShow) {
//		this.getObjectReturnList().clear();
//		if(!ListUtil.isNullOrEmpty(elementsToShow))
//			this.getObjectReturnList().addAll(elementsToShow);	
//		if(this.getBinder()!=null){
//			this.getBinder().loadComponent(this.getGrdSearchResult());
//		}	
		
		ListModelList gridModel = (ListModelList) grdSearchResult.getModel();
		// gridModel must not be null
		// if null, there is a problem in zk page
		// set initial model grid as new arraylist:
		// <grid ... model="@{new java.util.ArrayList()}">
		if(gridModel!=null){
			gridModel.clear();
			if (elementsToShow != null) {
				gridModel.addAll(elementsToShow);
			}
		}			
		else{
			this.objectReturnList=elementsToShow;
		}			
		
		
	}

	public Grid getGrdSearchResult() {
		return grdSearchResult;
	}

	public void setGrdSearchResult(Grid grdSearchResult) {
		this.grdSearchResult = grdSearchResult;
	}

	public List<Object> getObjectReturnList() {
		return objectReturnList;
	}

	public void setObjectReturnList(List<Object> objectReturnList) {
		this.objectReturnList = objectReturnList;
	}

	public EventListener getCallback() {
		return callback;
	}

	public void setCallback(EventListener callback) {
		this.callback = callback;
	}
	
	

	public Boolean getShowedLabelResult() {
		return showedLabelResult;
	}


	public void setShowedLabelResult(Boolean showedLabelResult) {
		this.showedLabelResult = showedLabelResult;
	}
	
	
	
	
}
